package com.camus.verify.bo;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;

import com.camus.utils.BigDecimalUtils;
import com.camus.verify.entity.VerifyTaskAp;
import com.camus.verify.entity.VerifyTaskInvoice;

public class SpecWithPriceHelper {

	private SpecWithPriceHelper() {
	}

	public static SpecWithPrice valueOf(final VerifyTaskInvoice t) {
		Objects.requireNonNull(t);
		Objects.requireNonNull(t.getSpec());
		Objects.requireNonNull(t.getPrice());
		return new SpecWithPrice(t.getSpec(), t.getPrice());
	}

	public static SpecWithPrice valueOf(final VerifyTaskAp t) {
		Objects.requireNonNull(t);
		Objects.requireNonNull(t.getSpec());
		Objects.requireNonNull(t.getSettledPrice());
		return new SpecWithPrice(t.getSpec(), t.getSettledPrice());
	}

	public static boolean equalsWithinTolerance(final SpecWithPrice l, final SpecWithPrice r,
			final BigDecimal tolerance) {
		Objects.requireNonNull(l);
		Objects.requireNonNull(r);
		Objects.requireNonNull(tolerance);
		if (BigDecimal.ZERO.compareTo(tolerance) == 0) {
			return l.equals(r);
		}
		return l.getSpec().equals(r.getSpec())
				&& BigDecimalUtils.isEqualWithinTolerance(l.getPrice(), r.getPrice(), tolerance);
	}

	public static Map<SpecWithPrice, List<VerifyTaskAp>> groupingAp(final List<VerifyTaskAp> list) {
		return groupingApWithinTolerance(list, BigDecimal.ZERO);
	}

	public static Map<SpecWithPrice, List<VerifyTaskAp>> groupingApWithinTolerance(final List<VerifyTaskAp> list,
			final BigDecimal tolerance) {
		if (list == null || list.size() == 0) {
			return Collections.emptyMap();
		}
		Objects.requireNonNull(tolerance);
		if (BigDecimal.ZERO.compareTo(tolerance) == 0) {
			return list.stream().collect(Collectors.groupingBy(SpecWithPriceHelper::valueOf));
		}

		Map<SpecWithPrice, List<VerifyTaskAp>> map = new HashMap<>();
		for (VerifyTaskAp t : list) {
			SpecWithPrice k = valueOf(t);
			List<VerifyTaskAp> v = new ArrayList<>();
			for (Entry<SpecWithPrice, List<VerifyTaskAp>> entry : map.entrySet()) {
				SpecWithPrice key = entry.getKey();
				if (equalsWithinTolerance(key, k, tolerance)) {
					v = entry.getValue();
					break;
				}
			}
			v.add(t);
			String spec = v.get(0).getSpec();
			BigDecimal sum = v.stream().map(VerifyTaskAp::getSettledPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
			BigDecimal price = sum.divide(new BigDecimal(v.size()), 2, RoundingMode.HALF_UP);
			map.put(new SpecWithPrice(spec, price), v);
		}
		return map;
	}

	public static Map<SpecWithPrice, List<VerifyTaskInvoice>> groupingInvoice(final List<VerifyTaskInvoice> list) {
		return groupingInvoiceWithinTolerance(list, BigDecimal.ZERO);
	}

	public static Map<SpecWithPrice, List<VerifyTaskInvoice>> groupingInvoiceWithinTolerance(
			final List<VerifyTaskInvoice> list, final BigDecimal tolerance) {
		if (list == null || list.size() == 0) {
			return Collections.emptyMap();
		}
		Objects.requireNonNull(tolerance);
		if (BigDecimal.ZERO.compareTo(tolerance) == 0) {
			return list.stream().collect(Collectors.groupingBy(SpecWithPriceHelper::valueOf));
		}

		Map<SpecWithPrice, List<VerifyTaskInvoice>> map = new HashMap<>();
		for (VerifyTaskInvoice t : list) {
			SpecWithPrice k = valueOf(t);
			List<VerifyTaskInvoice> v = new ArrayList<>();
			for (Entry<SpecWithPrice, List<VerifyTaskInvoice>> entry : map.entrySet()) {
				SpecWithPrice key = entry.getKey();
				if (equalsWithinTolerance(key, k, tolerance)) {
					v = entry.getValue();
					break;
				}
			}
			v.add(t);
			String spec = v.get(0).getSpec();
			BigDecimal sum = v.stream().map(VerifyTaskInvoice::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
			BigDecimal price = sum.divide(new BigDecimal(v.size()), 2, RoundingMode.HALF_UP);
			map.put(new SpecWithPrice(spec, price), v);
		}
		return map;
	}
}
